廢話不多說,先來個 demo!
祝CYBERの audio / VISUAL開張大吉。
大部分的技術性系列文章不免俗的都要從技術的各種基礎知識開始講起,但是這邊不採用這種方式。因為從思想解放與aesthEtic的觀點來看,modern web 裡面的可能性已經不是三言兩語講得完的向度。無限的吧。哇!
除了快點動動手指頭打個源代碼來安慰自已寂寞的心靈以外,對我而言,最重要還是動動小腦筋想想這個美麗的寶島還缺少了什麼愛,像台灣是對於 WebGL 的愛,還有文創產業。哈哈說個笑話。簡單來說,老斯今天想要講得就是如何做一個星空。相信星空大家應該都沒去過,但應該都看過。根據希臘古人說過,星空看起來應該是一個繞著地球轉的圓球,上面鑲嵌著許多閃亮的星星。豪氣!今天就來做星星!
一閃一閃亮晶晶,滿天都是小星星。看來我需要找一個 particle system 來開始改吧。你問我什麼是 particle system,我就會說:「就是很多的粒子囉。」只要曾經碰過一些畫圖 library 的人就會知道,當你想要用 for loop 來畫一千顆粒子跑來跑去的時候,會覺得很美。但是想要多一點的時候,就發現電腦風扇很涼。沒錯,你猜得沒錯,我需要一套利用 GPU 來算圖 (render) 的 particle system。我來找找看!
Yo!我找到了!
這回合我覆蓋一張陷阱卡,使用魔法卡「Three.js」!先不要管他怎麼做到的,用就是了。
這個 example 有點像是流星,遇到大氣層之後受熱產生碎屑的樣子。右上角的控制區還可以,潮棒的!把 lifetime 調長一點就會多些 particle、turbulance 開大一點就會像是風很大!下一步就是把這個 example 分離出來,以方便清洗。
這個就是我整理的 file structure。
three-stars/
├── 01
│ ├── index.html
│ ├── js
│ │ ├── GPUParticleSystem.js
│ │ ├── controls
│ │ │ └── TrackballControls.js
│ │ └── libs
│ │ ├── dat.gui.min.js
│ │ └── stats.min.js
│ └── textures
│ ├── particle2.png
│ └── perlin-512.png
└── build
└── three.js
6 directories, 8 files
先到 three.js 的 repo 裡面去找 examples 的資料夾,並把下面那個webgl_gpu_particle_system.html
的檔案改成index.html
(其他的就是跟他資料夾類似,只是把沒用的東西清掉),接下來就是 serve 這個資料夾。喔對了,希望大家都有自己使用 local server 的方法,我自己是使用 serve。沒問題的話應該會看到下面這樣,用 scroll 可以 zoom in/out,用 drag 可以旋轉鏡頭角度:
來玩玩看這個系統。
在index.html
裡面,前面落落長其實只不過是設定一堆東西,相信只要耐心閱讀 doc 就可以理解。實際源代碼的執行就是下面這兩個函式,init
設定環境,而animate
裡面則進行requestAnimationFrame( animate );
讓畫面不斷進行。
init();
animate();
而仔細看一下就會發現,在animate
裡面:
options.position.x = Math.sin( tick * spawnerOptions.horizontalSpeed ) * 20;
options.position.y = Math.sin( tick * spawnerOptions.verticalSpeed ) * 10;
options.position.z = Math.sin( tick * spawnerOptions.horizontalSpeed + spawnerOptions.verticalSpeed ) * 5;
for ( var x = 0; x < spawnerOptions.spawnRate * delta; x++ ) {
particleSystem.spawnParticle( options );
}
這一段負責產生粒子們,每個 frame(就是執行一次 animate 的過程)會產生 spawnerOptions.spawnRate * delta
顆粒子。也就是說,只要改變options
就可以控制粒子狀態了!
將上面提到的設定位置的那幾行 comment 掉,自己 DIY 把粒子射出來!哇!是三角函數欸!小朋友們,還記得球要怎麼用參數式表達嘛?
寫個 function 產生options.position
:
function sphere() {
const r = 40;
const theta = Math.random() * Math.PI * 2;
const phi = Math.random() * Math.PI;
const xpos = r * Math.sin(phi) * Math.cos(theta);
const ypos = r * Math.sin(phi) * Math.sin(theta);
const zpos = r * Math.cos(phi);
return ({
x: xpos,
y: ypos,
z: zpos,
});
}
並在每次射出的時候:
for ( var x = 0; x < spawnerOptions.spawnRate * delta; x++ ) {
options.position = sphere();
particleSystem.spawnParticle( options );
}
可能你覺得這樣看起來還不太像,那我們就來調整一下初始參數:
options = {
position: new THREE.Vector3(),
positionRandomness: .3,
velocity: new THREE.Vector3(),
velocityRandomness: .5,
color: 0xaa88ff,
colorRandomness: .2,
turbulence: .5,
lifetime: 2,
size: 5,
sizeRandomness: 1
};
spawnerOptions = {
spawnRate: 15000,
horizontalSpeed: 1.5,
verticalSpeed: 1.33,
timeScale: 1
};
options = {
position: new THREE.Vector3(),
positionRandomness: .3,
velocity: new THREE.Vector3(),
velocityRandomness: .5,
color: 0xaa88ff,
colorRandomness: .2,
turbulence: 1.0,
lifetime: 10,
size: 5,
sizeRandomness: 1
};
spawnerOptions = {
spawnRate: 30000,
horizontalSpeed: 10,
verticalSpeed: 10,
timeScale: 0.1,
};
喔!星星數不清。
仔細看,那是個銀河系吧?為什麼 random 產生的星空會有銀河系,難道是外星生物存在的證據?把他們的座標放進地球的隨機參數裡面嗎?拉遠了來看才發現,原來是球兩極聚集了許多粒子。動動小腦筋,原來是 random picking 的問題啊!
消滅銀河系行動就此展開:
function sphereEvenly() {
const r = 40;
const theta = Math.random() * Math.PI * 2;
const zpos = Math.random() * 2 - 1;
const rsin = Math.sqrt(1 - zpos * zpos)
const xpos = rsin * Math.cos(theta);
const ypos = rsin * Math.sin(theta);
return ({
x: r * xpos,
y: r * ypos,
z: r * zpos,
});
}
少男少女們!開啟全螢幕,接上投影機在天花板打上星空孤單的流淚吧!
心中想講什麼,我就把它說出來。明天繼續創世紀,試試使用其他數學模型的實驗。
聲音方面,該來的會來。
關於作者
Vibert Thio
致力於將對於技術的深度研究轉化為新型態藝術創作的能量,並思考技術的拓展/侷限與其對於藝術論述/呈現的影響。專長為數位藝術創作、音像程式設計、互動設計,喜愛即時運算的臨場感與不可預測。